6장. 공생성(Connascence)

출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 장연호 옮김, 제이펍 2026) | 공식: https://www.jpub.kr/

"두 모듈이 함께 태어났다" — 한 쪽을 바꾸면 다른 쪽도 따라 바뀌어야 하는 모든 관계를 공생성(connascence)이라 부른다. 5장의 6수준 모듈 결합보다 세밀하고, 정적(컴파일 타임)과 동적(런타임) 두 축으로 나눠 9가지 종류를 구분한다. — 메일리어 페이지존스(Meilir Page-Jones, 1996)


학습 목표

이 장을 끝내면 다음을 할 수 있다.

  • 공생성의 어원·정의와 모듈 결합과의 차이를 설명한다.
  • 정적 공생성 5가지(이름·유형·의미·알고리즘·위치)를 코드와 비유로 구분한다.
  • 동적 공생성 4가지(실행·타이밍·값·동일)를 코드와 비유로 구분한다.
  • 두 모듈 간 공생성이 여러 개 겹칠 때 전체 수준을 판단한다(가장 높은 수준이 대표).
  • 높은 수준 공생성을 낮은 수준으로 리팩토링하거나, 낮출 수 없을 때 이유를 설명한다.
  • 구조적 설계의 모듈 결합과 공생성의 사각지대를 설명하고 통합 강도(7장 선수)가 왜 필요한지 말한다.

전체 흐름도

[ 공생성 = "함께 태어남" — 한쪽 변경 시 다른쪽도 변경 필요 ]
        │
        ├─ 정적 공생성 (컴파일 타임 / 코드 읽어서 식별)
        │     │   공유 지식: 이름 → 유형 → 의미 → 알고리즘 → 위치 (점점 넓어짐)
        │     ├─ 1. 이름 공생성 (CoN)   ─ 가장 약함 / 가장 명시적
        │     ├─ 2. 유형 공생성 (CoT)
        │     ├─ 3. 의미 공생성 (CoM)
        │     ├─ 4. 알고리즘 공생성 (CoA)
        │     └─ 5. 위치 공생성 (CoP)   ─ 정적 중 가장 강함 / 가장 암묵적
        │
        └─ 동적 공생성 (런타임 / 실행 중 식별)
              │   → 정적 공생성 전체보다 더 강한 종속성
              ├─ 6. 실행 공생성 (CoE)   ─ 동적 중 가장 약함
              ├─ 7. 타이밍 공생성 (CoTi)
              ├─ 8. 값 공생성 (CoV)
              └─ 9. 동일 공생성 (CoI)   ─ 가장 강함

[ 여러 수준이 겹치면 → 가장 높은 수준이 전체 수준을 대표 ]
[ 높은 공생성 = "함께 살아야" → 억지로 낮추기보다 가까이 두는 설계 고려 ]
[ 두 모델의 사각지대 → 7장 통합 강도(Integration Strength)로 통합 ]

0. 사전 필수 용어

용어 정의
공생성 (Connascence) 라틴어 con(함께) + nascere(태어남). 두 모듈의 수명주기가 얽혀, 한 모듈을 바꿀 때 다른 모듈도 바꿔야 하는 관계
정적 공생성 (Static Connascence) 소스 코드·컴파일 타임 수준의 상호 연결. 코드 리뷰·정적 분석으로 식별 가능
동적 공생성 (Dynamic Connascence) 런타임 동작 수준의 상호 연결. 실행 중에야 드러나며 정적보다 강하다
마법 값 (Magic Value) 코드에 하드코딩된 숫자·문자열로, 그 의미를 코드만 보면 알 수 없는 값. 예: newStatus=7
위치 민감 인터페이스 인수 순서가 의미를 결정하는 인터페이스. 순서를 실수하면 조용히 틀린 결과가 나온다
트랜잭션 일관성 (Transactional Consistency) 여러 데이터 변경이 원자적으로 성공하거나 실패해야 하는 보장. 값 공생성·동일 공생성의 핵심 배경
열거형 (Enum) 의미 공생성을 줄이기 위한 리팩토링 도구. 마법 값 → 이름 있는 상수로 격상
모듈 결합 vs 공생성 모듈 결합(5장)은 통신 방식을 6단계로 분류, 공생성은 공유 지식의 종류를 9단계로 분류. 동일 현상의 다른 측면
공생성 전체 수준 두 모듈 사이에 여러 공생성이 동시에 존재할 때, 가장 높은 수준이 전체 공생성을 대표
통합 강도 (Integration Strength) 모듈 결합과 공생성의 사각지대를 보완하는 7장의 통합 모델. 4수준(계약·모델·기능·침입 결합)으로 구성

1. 공생성이란? — "함께 태어난" 모듈

일상 비유: 쌍둥이를 떠올려 보자. 한 아이가 독감에 걸리면 부모는 다른 아이도 검사해야 한다. 별개 아이들이면 "나만 아프면 그만"이지만, 함께 태어나 같은 공간에서 지내는 쌍둥이는 한 명의 변화가 자동으로 다른 명의 검토를 요구한다.

소프트웨어에서 공생성이 정확히 이 관계다. 한 모듈을 변경할 때 다른 모듈도 변경해야 하거나, 최소한 세심하게 검토해야 한다면, 둘은 공생성 관계다.

또한 두 모듈을 동시에 변경해야 할 합리적인 요구사항 변경이 예상되는 경우에도 공생성 관계로 볼 수 있다.

5장의 모듈 결합이 "어떤 방식으로 연결됐나(전역 변수인가, 플래그인가, 데이터만인가)"를 물었다면, 공생성은 "어떤 종류의 지식을 공유해야 하는가"를 더 세밀하게 물어본다.

핵심 통찰 — 공생성 수준이 높을수록 두 모듈은 더 많은 지식을 함께 알아야 하고, 한 쪽 변경이 다른 쪽에 미치는 충격이 커진다. 반대로 공생성이 낮으면 한 쪽이 바뀌어도 다른 쪽은 그대로일 수 있다.


2. 정적 공생성 — 코드만 읽어도 보이는 결합

정적 공생성은 소스 코드 수준의 상호 의존. 이론적으로 정적 코드 분석 도구로 자동화할 수 있다. 공유 지식은 이름에서 위치로 갈수록 넓어지고, 인터페이스는 명시적에서 암묵적으로 바뀐다(그림 6.1 원서).

2.1 이름 공생성 (CoN) — 같은 이름표를 달아야 한다

일상 비유: 아파트 우편함에 이름표가 붙어 있다. 택배기사는 이름표에 적힌 이름을 보고 문을 두드린다. 내가 이름을 바꾸면 택배기사에게 새 이름표를 알려야 한다 — 그렇지 않으면 택배가 영영 도착 안 한다.

소프트웨어: 연결된 모듈이 같은 이름으로 같은 것을 가리켜야 하는 관계. 변수명·메서드명·서비스 엔드포인트 이름이 모두 여기 해당한다.

잘못된 예 / 올바른 예

# 잘못된 예: name을 nm으로 바꾸면 두 행이 동시에 깨짐
def greet(name):              # 01행 — name 정의
    message = f'Hello, {name}!'  # 02행 — name 사용 (이름 공생성)
    print(message)            # 03행 — message 사용 (이름 공생성)

greet('world')               # 05행 — greet 이름 공유 (이름 공생성)

# 올바른 예: 이름 변경은 IDE 리네임으로 한 번에 — 이름 공생성은 불가피하지만 관리 가능
def greet(name: str) -> None:
    message = f'Hello, {name}!'
    print(message)

greet('world')

이름 공생성은 가장 약한 수준이다. 현대 IDE의 "리네임 리팩토링" 한 번으로 대부분 해결된다. 불가피한 결합이기도 하다 — 이름이 없으면 서로를 가리킬 수 없다. 같은 근거가 클래스 공개 메서드, 웹 서비스 HTTP 동사·엔드포인트에도 동일하게 적용된다.


2.2 유형 공생성 (CoT) — 같은 형식의 서류를 써야 한다

일상 비유: 해외 취업 서류는 반드시 영어로 제출해야 한다. 이름을 맞게 써도 언어(형식)가 틀리면 무효다. 한국어로 완벽하게 쓴 자소서는 받아주지 않는다.

소프트웨어: 이름뿐 아니라 타입도 동일해야 하는 관계. 강타입 언어(C#, Java, TypeScript)에서 두드러지고, 동적 타입 언어(Python, JS)에서도 암묵적 타입 가정은 남아 있다. 다른 유형의 값이 제공되면 코드는 유효하지만 실행 중에 실패할 수 있다.

잘못된 예 / 올바른 예

// 잘못된 예: name 인수에 int를 넘기면 컴파일 오류 (유형 공생성 위반)
private static void Greet(string name) {
    string message = $"Hello, {name}";
    Console.WriteLine(message);
}

static void Main() {
    Greet("world");   // string — 올바름
    Greet(42);        // int — 컴파일 오류 (유형 공생성 위반)
}

// 올바른 예: 계약을 명시적으로 선언해 컴파일러가 검사하게
private static void Greet(string name) {
    Console.WriteLine($"Hello, {name}");
}

유형 공생성은 이름 공생성보다 한 단계 강하다. 둘은 거의 항상 함께 나타나며, 유형에 독립적인 통합은 매우 드물다.


2.3 의미 공생성 (CoM) — 비밀 암호를 둘이 나눠 갖는다

일상 비유: 도서관 내부 코드 시스템에서 장르코드 = 7이 "SF"를 뜻한다고 사서와 서가 담당자가 약속했다. 새 직원이 7의 의미를 모르면 책을 엉뚱한 곳에 꽂는다. 코드북을 공유하지 않으면 아무도 알 수 없는 마법 값이다.

소프트웨어: 두 모듈이 특정 값에 특별한 의미를 부여하기로 암묵적으로 합의한 관계. 이 의미는 코드만 봐서는 알 수 없다. 컴파일러가 검증할 수 없고, 실수하기 쉽다.

잘못된 예 / 올바른 예

# 잘못된 예: 7이 무슨 뜻인지 코드만으로 알 수 없음 (의미 공생성)
def process_email(msg, case_id):
    support_case = repo.load(case_id)
    support_case.append_response(msg.body, new_status=7)  # 7 = Reopened???

# 올바른 예: 열거형으로 의미를 명시 → 의미 공생성 → 유형+이름 공생성으로 격상
from enum import Enum

class Status(Enum):
    Open      = 0
    FollowUp  = 1
    OnHold    = 2
    Escalated = 3
    Closed    = 4
    Resolved  = 5
    Reopened  = 6

def process_email(msg, case_id):
    support_case = repo.load(case_id)
    support_case.append_response(msg.body, new_status=Status.Reopened)  # 명확!

열거형 하나로 마법 값을 이름 있는 상수로 바꾼다. 컴파일러가 유효하지 않은 값을 잡아주고, 인터페이스가 자기 설명적이 된다. 기술적으로는 이름 공생성+유형 공생성 둘 다이지만, 전통적으로 가장 높은 수준만 표기한다.


2.4 알고리즘 공생성 (CoA) — 암호 해독법을 둘이 같이 알아야 한다

일상 비유: 스파이 영화의 암호 통신을 떠올려 보자. 발신자와 수신자가 같은 암호 방식을 써야 메시지가 해독된다. 발신자는 카이사르 암호를 쓰고 수신자가 비즈네르 암호로 해독하려 하면 — 아무 의미 없는 글자들이 나온다.

소프트웨어: 두 모듈이 교환하는 값의 의미를 이해하기 위해 같은 알고리즘을 사용하기로 합의한 관계. 데이터 암호화·해시·직렬화 방식이 전형적인 예다.

중요한 오해 방지: 알고리즘 공생성은 코드 중복에 관한 것이 아니다. 알고리즘이 외부 라이브러리에 있든 복제돼 있든, 둘이 같은 알고리즘에 동의해야 한다는 사실이 핵심이다. 이 때문에 공생성 척도에서 비교적 낮은 순위(9점 만점에 4점)를 받는다.

잘못된 예 / 올바른 예

# 잘못된 예: 업로드 쪽은 MD5, 다운로드 쪽은 SHA-256 → 항상 불일치
def upload_file(file_path: str):
    data = read_file(file_path)
    checksum = calculate_md5(data)      # MD5 사용
    storage.upload(data, checksum)

def verify_file(data: bytes, checksum: str) -> bool:
    return calculate_sha256(data) == checksum  # SHA-256 → 항상 실패

# 올바른 예: 알고리즘을 공유 상수로 단일화
HASH_ALGORITHM = "md5"  # 단일 출처 (Single Source of Truth)

def upload_file(file_path: str):
    data = read_file(file_path)
    checksum = calculate_checksum(data, HASH_ALGORITHM)
    storage.upload(data, checksum)

def verify_file(data: bytes, checksum: str) -> bool:
    return calculate_checksum(data, HASH_ALGORITHM) == checksum

2.5 위치 공생성 (CoP) — 줄 서는 순서가 의미를 결정한다

일상 비유: 공연 티켓 구매 양식에 "이름, 좌석번호, 공연일" 순으로 적어야 한다. 순서를 바꿔 "공연일, 이름, 좌석번호"로 적으면 시스템이 공연일을 이름으로, 이름을 좌석번호로 해석한다. 내용이 맞아도 위치가 틀리면 오류다.

소프트웨어: 여러 모듈이 요소의 순서에 합의해야 하는 관계. 배열 인덱스, 동일 타입 인수 목록, 이름 없는 튜플이 전형적인 예다.

잘못된 예 / 올바른 예

# 잘못된 예 A: 배열 인덱스로 의미를 결정 (코드 6.6 패턴 — 원서)
def send_email(data: list):
    from_addr = data[0]   # 순서 암묵 합의
    to_addr   = data[1]
    subject   = data[2]
    body      = data[3]

# 잘못된 예 B: 같은 타입 여러 인수 (코드 6.7 패턴) — from/to 순서를 실수하기 쉬움
def send_email(from_addr: str, to_addr: str, subject: str, body: str):
    ...

# 잘못된 예 C: 이름 없는 튜플 반환 (코드 6.8 패턴) — 어떤 값이 먼저인지 모름
def get_current_datetime():
    local_time = datetime.now()
    utc_time   = datetime.utcnow()
    return (local_time, utc_time)  # 순서 공유 필요

# 올바른 예: named 인수나 데이터 클래스로 위치 의존 제거
from dataclasses import dataclass

@dataclass
class EmailMessage:
    from_addr: str
    to_addr:   str
    subject:   str
    body:      str

def send_email(msg: EmailMessage):
    # 이제 순서는 중요하지 않고 이름이 의미를 결정
    ...

위치 공생성은 정적 공생성 중 가장 강한 수준이다. 겉보기에 이름 공생성과 비슷해 보이지만, 하나는 인터페이스를 명시적으로 만들고 다른 하나는 암묵적으로 남겨 오류를 유발한다. 통합 가이드라인을 참조하지 않고서는 안전하게 통합할 수 없다.


3. 동적 공생성 — 실행 중에야 보이는 결합

동적 공생성은 런타임 수준의 의존. 코드를 아무리 들여다봐도 실행해 보지 않으면 보이지 않는 결합이 여기 해당한다. 동적 공생성은 정적 공생성 전체보다 더 강한 종속성을 만들며, 모듈 간에 공유되는 지식의 범위가 더 넓어진다(그림 6.2 원서).

3.1 실행 공생성 (CoE) — 악기 합주의 입장 순서

일상 비유: 오케스트라 합주를 생각해 보자. 타악기가 먼저 들어오고, 그 다음에 현악기, 마지막에 관악기가 들어온다. 관악기가 현악기보다 먼저 나오면 연주가 망가진다. 순서 자체가 음악의 일부다.

소프트웨어: 모듈들이 특정 순서로 실행되어야 하는 관계. 정적 공생성에서의 위치 공생성에 대응하는 동적 공생성이다.

코드 예시

// 실행 공생성의 교과서 예 — DB 트랜잭션 순서를 어기면 런타임 오류 (코드 6.9 원서)
interface DbConnection {
    void OpenConnection();           // 1번 먼저 — 다른 모든 메서드의 전제
    void BeginTransaction(UUID id);  // 2번 — OpenConnection 후에만
    QueryResult ExecuteQuery(String sql); // 3번 — BeginTransaction 후에만
    void Commit(UUID id);            // 4번 — ExecuteQuery 후에만
    void Rollback(UUID id);          // 4번 대안
    void CloseConnection();          // 5번 마지막
}

// 잘못된 사용: 런타임 오류
conn.Commit(txId);       // 오류! OpenConnection 전에 커밋 시도
conn.OpenConnection();

// 올바른 사용: 정해진 순서
conn.OpenConnection();
conn.BeginTransaction(txId);
conn.ExecuteQuery("SELECT ...");
conn.Commit(txId);
conn.CloseConnection();

실행 공생성은 런타임 종속이기 때문에 컴파일러가 잡아주지 않는다. 메서드 간의 런타임 종속성은 기능이 서로 밀접하게 관련되어 있음을 나타낸다. Template Method 패턴, 상태 기계(State Machine)로 순서를 강제할 수 있다.


3.2 타이밍 공생성 (CoTi) — 정확한 배달 시간이 있는 택배

일상 비유: "오전 10시~12시 사이 배달"인 택배를 받으려면 그 시간에 집에 있어야 한다. 10시 5분에 나가면 택배가 왔다가 돌아간다. 순서뿐 아니라 시간 간격이 중요하다.

소프트웨어: 순서뿐 아니라 두 실행 사이의 시간 간격에도 의존하는 관계. DB 연결 타임아웃, 시스템 클록 이중 조회가 전형적인 예다. 실시간 시스템에는 타이밍 관계로 인한 공생성이 다양하게 존재한다(자동차 문 자동 잠금, X선 기계 비활성화 등).

잘못된 예 / 올바른 예

// 잘못된 예: 시스템 클록을 두 번 조회 (코드 6.10 원서)
// — 두 호출 사이에 분이 바뀌거나 일광절약 시간제 전환이 발생하면 잘못된 결과
(int, int) GetTime() {
    int hour   = DateTime.Now.Hour;   // 첫 번째 클록 조회 (02행)
    int minute = DateTime.Now.Minute; // 두 번째 클록 조회 (03행) — 시간차 가능
    return (hour, minute);
}
// 23:59에 hour=23, 그 사이 분 전환 시 minute=0 → 23:00이라는 오류

// 올바른 예: 클록을 한 번만 조회 (코드 6.11 원서) — 타이밍 의존 제거
(int, int) GetTime() {
    DateTime now = DateTime.Now; // 한 번만 — now.Hour, now.Minute은 같은 순간
    int hour   = now.Hour;
    int minute = now.Minute;
    return (hour, minute);
}

타이밍 공생성은 가장 발견하기 어려운 공생성 중 하나다. 타이밍 불일치는 특정 환경·부하 조건에서만 나타나고 재현이 어렵다. 이벤트/트랜잭션 타임스탬프 불일치는 잘못된 데이터 분석, 이벤트 순서 역전으로 이어질 수 있다.

주의: DB 연결 30초 타임아웃처럼 비즈니스 요구사항 자체가 타이밍 간격을 규정하는 경우, 리팩토링으로 완전히 제거할 수 없다. 구현 부주의로 생긴 타이밍 의존만 제거 대상이다.


3.3 값 공생성 (CoV) — 삼각형의 세 변은 혼자 바꿀 수 없다

일상 비유: 정삼각형에서 한 변의 길이를 늘리면 삼각형 모양을 유지하려면 다른 두 변도 바뀌어야 한다. 또는 소매점 재고 — 판매 수량을 올리면 재고 수량을 그만큼 줄여야 한다. 둘 다 안 바꾸면 장부가 틀린다. 한 값을 바꾸려면 연관된 다른 값도 함께 바꿔야 유효한 상태를 유지한다.

소프트웨어: 시스템의 여러 값이 동시에 변경되어야 하는 강한 기능적 관계. 산술 제약이나 비즈니스 규칙이 이 관계를 만든다. 한 값만 바꾸면 시스템이 불법 상태(illegal state)가 된다.

코드 예시

// 산술 제약에 의한 값 공생성 — 삼각형 세 변 (코드 6.12 원서)
class Triangle {
    double EdgeA { get; private set; }
    double EdgeB { get; private set; }
    double EdgeC { get; private set; }
    // 제약: A < B+C, B < A+C, C < A+B (삼각형 부등식)

    void SetEdges(double a, double b, double c) {
        if (a >= b + c || b >= a + c || c >= a + b)
            throw new InvalidOperationException("삼각형 조건 위반");
        EdgeA = a; EdgeB = b; EdgeC = c; // 세 개를 원자적으로 변경
    }
}

// 비즈니스 규칙에 의한 값 공생성 (코드 6.13 원서)
// — 고객 검증 취소 시 우선 배송 자격도 함께 취소
class Customer {
    bool isVerified { get; private set; }
    bool priorityShippingEnabled { get; private set; }

    void ClearVerification() {
        isVerified = false;
        priorityShippingEnabled = false; // 함께 바뀌어야 일관성 유지
    }

    void AllowPriorityShipping() {
        if (isVerified) {
            priorityShippingEnabled = true;
        }
    }
}

값 공생성이 있는 필드들은 단일 메서드로 묶어 원자적 변경을 강제하는 것이 전형적인 해결책이다.


3.4 동일 공생성 (CoI) — 같은 냄비에서 같이 끓어야 한다

일상 비유: 공유 주방이 있는 쉐어하우스에서 냄비가 딱 한 개뿐이다. 두 사람이 각자 별개 냄비를 사용하면 음식이 따로 익는다. 하지만 공동 냄비를 함께 써야 한다면, 한 사람이 냄비를 바꾸거나 치우면 다른 사람 요리가 즉시 영향받는다. 정확히 동일한 인스턴스를 공유해야 올바르게 동작하는 관계다.

소프트웨어: 두 모듈이 올바르게 동작하려면 세 번째 객체의 정확히 같은 인스턴스를 함께 참조해야 하는 가장 강한 공생성. 공유 객체의 모든 변경사항은 연결된 모듈에서 즉시 관찰 가능하다.

코드 예시

# 잘못된 예: 두 모듈이 각자 다른 DB 커넥션 풀 인스턴스 생성
# — 리소스 낭비 + 트랜잭션 일관성 보장 불가
class ModuleA:
    def __init__(self):
        self.pool = ConnectionPool()  # 자기만의 풀

class ModuleB:
    def __init__(self):
        self.pool = ConnectionPool()  # 또 다른 풀 — 커넥션 수 두 배 소비

# 올바른 예: 단일 인스턴스를 의존성 주입으로 공유 — 동일 공생성을 명시적으로 관리
shared_pool = ConnectionPool(max_connections=10)

class ModuleA:
    def __init__(self, pool: ConnectionPool):
        self.pool = pool  # 외부에서 주입받음

class ModuleB:
    def __init__(self, pool: ConnectionPool):
        self.pool = pool  # 같은 인스턴스 주입

module_a = ModuleA(shared_pool)
module_b = ModuleB(shared_pool)  # 정확히 같은 shared_pool 참조

동일 공생성은 가장 강한 수준이다. 분산 시스템에서는 그림 6.4(원서)처럼 두 서비스가 같은 DB에 쓰고 읽으며 트랜잭션 일관성을 가정할 때 동일 공생성이 발생한다. 단, 메시지 버스를 통해 통신하고 일관성을 가정하지 않으면 동일 공생성이 아닐 수 있다.


4. 공생성 평가하기 — 실전 예시 (코드 6.14 원서)

실전 코드에는 여러 공생성이 동시에 나타난다. 다음 호출을 분석해 보자.

# retail 모듈 → accounting 모듈로의 호출
(res, balance, tran_id) = accounting.process_payment(
    account_id='LVG141028',
    transaction_type=3,           # 마법 값 3 = 어떤 의미?
    credit_card='S5hDn175mPiDL4D5ftbtMw=='  # AES 암호화된 값
)

이 한 줄에서 발생하는 공생성:

공생성 수준 발생 이유
이름 공생성 process_payment, account_id, transaction_type, credit_card 이름 공유
유형 공생성 account_id·credit_card는 문자열, transaction_type은 숫자여야 함
의미 공생성 transaction_type=3의 의미를 두 모듈이 암묵 공유
알고리즘 공생성 credit_card 값을 AES 알고리즘으로 암호화·복호화에 합의
위치 공생성 반환값 튜플 (res, balance, tran_id)의 순서를 공유

전체 수준: 위치 공생성(가장 높은 수준). 여러 공생성이 겹칠 때는 가장 높은 수준이 전체를 대표한다. 단, 동적 공생성 가능성도 있으므로 실제로는 더 높을 수 있다.


5. 공생성 관리하기

공생성은 항상 낮출 수 있는 것이 아니다. 리팩토링으로 낮출 수 있는 경우와 없는 경우를 구분하는 것이 실무의 핵심이다.

공생성 리팩토링 방법 낮출 수 있나?
의미 → 유형/이름 열거형 추출 대부분 가능
위치 → 이름 Named 인수 / 데이터 클래스 가능
타이밍 클록 단일 조회 구현 부주의는 가능, 비즈니스 요구는 불가
알고리즘 알고리즘 통일 경우에 따라
원자적 변경 강제 비즈니스 요구는 제거 불가
동일 의존성 주입으로 명시화 요구 자체는 제거 불가

핵심 통찰 — 높은 공생성을 낮추려는 목표가 항상 옳은 것은 아니다. 높은 공생성은 그 모듈들이 함께 살아야 한다는 신호이기도 하다. 분리하는 것보다 가까이 두는 것이 더 나은 선택일 수 있다(8장 거리·10장 결합 균형에서 자세히 다룬다).


6. 모듈 결합 vs 공생성: 두 모델의 한계와 사각지대

6.1 콘텐츠 결합의 역설

// 코드 6.15 원서 — 리플렉션으로 비공개 속성 접근
var customer = LoadNextCustomer();
var value = typeof(Customer)
    .GetProperty("_verificationStatus")
    .GetValue(customer);
  • 모듈 결합 관점: 비공개 인터페이스 사용 → 가장 강한 콘텐츠 결합
  • 공생성 관점: 비공개 필드 이름(_verificationStatus)만 알면 됨 → 가장 약한 이름 공생성

이 역설은 두 모델이 다른 측면을 측정하기 때문이다. 모듈 결합은 인터페이스 유형을, 공생성은 공유 지식의 복잡도를 측정한다.

6.2 두 모델의 공통 사각지대 — 물리적 통합 없는 지식 공유

두 서비스가 완전히 동일한 비즈니스 로직(예: 무료 배송 자격 규칙)을 각자 구현한 경우:

# Retail 서비스와 Fulfillment 서비스가 동일한 비즈니스 로직을 중복 구현
def is_qualified_for_free_shipping(order):
    return active_promotions.apply_for(order) or order.total_cost > 1000

# → 물리적 연결 없음 → 모듈 결합도 아님, 알고리즘 공생성도 아님
# → 그러나 규칙 변경 시 두 서비스를 동시에 변경해야 함 → 강한 종속성 존재

이 상황은 모듈 결합도, 공생성도 포착하지 못한다. 두 모델의 공통 사각지대다. → 7장 통합 강도 (Integration Strength)가 이를 해결한다.


핵심 개념 정리

공생성 유형 핵심 신호 비유
이름 (CoN) 정적 이름 하나 바꾸면 두 곳이 깨짐 우편함 이름표
유형 (CoT) 정적 타입 불일치 시 컴파일 오류 해외 서류 언어 형식
의미 (CoM) 정적 마법 값·하드코딩 상수 도서관 내부 코드번호
알고리즘 (CoA) 정적 암호화·해시 알고리즘 합의 스파이 암호 해독법
위치 (CoP) 정적 배열 인덱스·인수 순서 의존 티켓 양식 항목 순서
실행 (CoE) 동적 메서드 호출 순서 강제 오케스트라 입장 순서
타이밍 (CoTi) 동적 두 동작 사이 시간 간격 의존 배달 시간 맞추기
값 (CoV) 동적 여러 값이 동시에 변경되어야 일관성 유지 삼각형 세 변
동일 (CoI) 동적 정확히 같은 인스턴스를 참조해야 작동 쉐어하우스 공동 냄비

실무 체크리스트

  • [ ] 코드에 숫자·문자열 마법 값이 모듈 경계를 넘어 공유되는가? → 열거형으로 의미 공생성을 낮춰라.
  • [ ] 배열 인덱스나 동일 타입 인수 목록으로 의미를 전달하는 함수가 있는가? → Named 인수나 데이터 클래스로 위치 공생성을 낮춰라.
  • [ ] 두 모듈이 같은 데이터를 암호화·해시할 때 알고리즘을 공유 상수나 라이브러리로 단일화했는가?
  • [ ] API·인터페이스를 설계할 때 반환값이 이름 없는 튜플인가? → 이름 있는 객체로 위치 공생성을 제거하라.
  • [ ] 시스템 클록을 짧은 시간에 여러 번 조회하는 코드가 있는가? → 한 번 조회해 변수에 담고 재사용하라.
  • [ ] 높은 공생성(값·동일)을 억지로 낮추려 하고 있는가? → 해당 모듈들을 가까이 두는 방향을 먼저 고려하라.
  • [ ] 여러 공생성이 겹치는 인터페이스에서 전체 공생성 수준을 "가장 높은 것"으로 판단했는가?
  • [ ] 두 모듈이 물리적으로 연결되지 않아도 동일한 비즈니스 로직을 중복 구현하고 있지는 않은가? → 모듈 결합·공생성 모델의 사각지대이므로 통합 강도(7장) 관점으로 점검하라.

연습문제 (문제만 — 정답은 부록 D)

  1. 분류. 다음 코드의 공생성 수준은 무엇인가? 근거를 한 문장으로 답하라. python def calculate_area(width, height): return width * height area = calculate_area(10, 5) # 인수 순서를 실수로 바꾸면? 힌트: 이름과 위치를 모두 따져보라.

  2. 리팩토링. 다음 코드의 의미 공생성을 유형/이름 공생성으로 낮춰라. python def set_order_status(order_id, status_code): # status_code: 1=pending, 2=shipped, 3=delivered, 4=cancelled db.update("orders", {"status": status_code}, {"id": order_id})

  3. 분석. 두 마이크로서비스 A·B가 Redis의 같은 키에 각자 데이터를 쓰고, 서로의 변경 사항을 즉시 읽는다. 어느 수준의 공생성인가? 그 이유는?

  4. 판단. 타이밍 공생성이 있는 시스템에서 "리팩토링으로 타이밍 공생성을 완전히 제거하자"는 주장이 옳은가? DB 연결 30초 타임아웃을 예로 들어 답하라.

  5. 종합. 코드 6.14의 process_payment 호출에서 공생성 수준을 하나씩 열거하고, 각각을 어떻게 낮출 수 있는지 제안하라. 낮출 수 없는 것이 있다면 이유를 설명하라.


최신 동향 (검증 2026-05-21)

공생성 모델 자체(Page-Jones 1996)는 안정된 이론이며 내용 변경 없이 유효하다. 다만 현대 도구·아키텍처와의 접점이 성숙하고 있다.

  • connascence.io에서 9가지 공생성을 인터랙티브하게 탐색할 수 있다. 각 수준별 강도(degree)·지역성(locality) 가이드라인도 제공한다.
  • OpenAPI / AsyncAPI 같은 계약 우선(contract-first) 도구가 확산되면서, 의미·알고리즘·위치 공생성을 명시적 계약으로 고정하는 방법이 표준화되고 있다. 이름·유형 공생성은 계약으로 표현하기 쉽지만, 값·동일 공생성은 여전히 운영 설계로 관리해야 한다.
  • gRPC·Protobuf 같은 이진 직렬화 프로토콜은 알고리즘·위치 공생성을 스키마 수준에서 고정하여 런타임 불일치를 컴파일 타임 오류로 앞당기는 실천이 확산되고 있다.

부록 A. 용어 사전

한글 용어 원문 영문명 의미
공생성 Connascence "함께 태어남" — 한 모듈 변경 시 다른 모듈도 변경해야 하는 관계
정적 공생성 Static Connascence 소스 코드·컴파일 타임 수준의 공생성
동적 공생성 Dynamic Connascence 런타임 수준의 공생성. 정적보다 강함
이름 공생성 Connascence of Name (CoN) 같은 이름을 공유해야 하는 가장 약한 공생성
유형 공생성 Connascence of Type (CoT) 같은 타입을 공유해야 하는 공생성
의미 공생성 Connascence of Meaning (CoM) 마법 값의 의미를 공유해야 하는 공생성
알고리즘 공생성 Connascence of Algorithm (CoA) 같은 알고리즘(암호화 등)을 사용해야 하는 공생성
위치 공생성 Connascence of Position (CoP) 요소의 순서를 공유해야 하는 공생성. 정적 최강
실행 공생성 Connascence of Execution (CoE) 특정 순서로 실행되어야 하는 동적 공생성
타이밍 공생성 Connascence of Timing (CoTi) 시간 간격에 의존하는 동적 공생성
값 공생성 Connascence of Value (CoV) 여러 값이 동시에 변경되어야 하는 동적 공생성
동일 공생성 Connascence of Identity (CoI) 정확히 같은 인스턴스를 참조해야 하는 가장 강한 공생성
마법 값 Magic Value 의미를 알아야만 해독할 수 있는 하드코딩된 값
공생성 전체 수준 Overall Connascence Level 여러 공생성이 겹칠 때 가장 높은 수준이 대표
통합 강도 Integration Strength 7장에서 모듈 결합+공생성의 사각지대를 보완하는 통합 모델

부록 B. 핵심 비교표

9가지 공생성 한눈에

번호 이름 유형 강도 식별 방법 주요 리팩토링
1 이름 정적 가장 약함 이름 불일치 → 컴파일/런타임 오류 IDE 리네임
2 유형 정적 약함 타입 불일치 → 컴파일 오류 타입 명시화
3 의미 정적 중하 마법 값·하드코딩 상수 열거형 추출
4 알고리즘 정적 알고리즘 합의 필요 공유 라이브러리 단일화
5 위치 정적 중상 인수·반환 순서 의존 Named 인수·데이터 클래스
6 실행 동적 순서 위반 시 런타임 오류 Template Method·상태 기계
7 타이밍 동적 시간차로 인한 간헐 오류 클록 단일 조회·동기화
8 동적 매우 강함 불일치 시 불법 상태 원자적 변경 묶기
9 동일 동적 가장 강함 인스턴스 분리 시 오동작 의존성 주입·싱글턴

정적 vs 동적 공생성

구분 정적 공생성 동적 공생성
식별 시점 코드 리뷰·정적 분석 런타임 실행
강도 상대적으로 약함 정적 전체보다 강함
자동화 정적 분석 도구 가능 런타임 모니터링 필요
이름·유형·의미·알고리즘·위치 실행·타이밍·값·동일

5장 모듈 결합 vs 6장 공생성 관계

모듈 결합(5장) 관련 공생성(6장) 설명
데이터 결합 모든 정적 공생성 가능 통합에 필요한 최소 데이터만 교환
스탬프 결합 유형 이상 정적 공생성 필요 이상의 데이터 구조 공유
제어 결합 공생성 모델에 직접 대응 없음 내부 분기를 외부가 제어하는 구조
외부 결합 동일 공생성(최종) 전역 변수 → 강한 일관성 가정
공통 결합 동일 공생성(최종) 외부 결합의 확장판
콘텐츠 결합 이름 공생성(의외!) 비공개 필드명만 알면 되는 역설

흥미로운 역설 — 5장에서 가장 강한 콘텐츠 결합이, 공생성 관점에서는 가장 약한 이름 공생성에 해당할 수 있다. 두 모델은 같은 현상의 다른 측면을 측정하기 때문이다. 이 역설이 7장 통합 강도 모델의 출발점이다.


부록 C. 추천 참고 자료 & 링크

Tier 1 공식·표준 (생존 확인 2026-05-21)

자료 링크
책 공식 (제이펍) jpub.kr
원서 — Manning Balancing Coupling in Software Design
공생성 인터랙티브 가이드 connascence.io
Page-Jones 원저 What Every Programmer Should Know About Object-Oriented Design (Dorset House, 1996)
OpenAPI 계약 우선 표준 openapis.org
AsyncAPI (비동기 API 계약) asyncapi.com

책 다른 장 안내

설명
5장 모듈 결합 6수준 — 6장 공생성의 선수 모델
7장 통합 강도 — 5장 모듈 결합 + 6장 공생성을 통합한 실용 모델 (4수준: 계약·모델·기능·침입 결합)
8장 거리 — 높은 공생성 모듈을 가까이 두는 설계 원리
10장 결합 균형 — 공생성 수준별 언제 받아들일 만한가

부록 D. 연습문제 풀이

1. 분류

위의 calculate_area(width, height) 호출은 이름 공생성유형 공생성이 함께 있다. 함수명 calculate_area와 인수명 width·height를 공유하는 이름 공생성, 두 인수가 숫자 타입이어야 하는 유형 공생성이다. calculate_area(10, 5) 호출에서 width=10·height=5로 순서를 가정하지만, 이 경우는 곱셈의 교환법칙이 성립하므로 위치 공생성의 실질적 위험은 없다. 단, 동일 타입 여러 인수를 쓰는 구조 자체는 위치 공생성 신호이므로 Named 인수 사용이 권장된다.

2. 리팩토링 — 의미 공생성 제거

# 열거형으로 status_code 마법 값 제거
from enum import Enum

class OrderStatus(Enum):
    Pending   = 1
    Shipped   = 2
    Delivered = 3
    Cancelled = 4

def set_order_status(order_id: str, status: OrderStatus):
    db.update("orders", {"status": status.value}, {"id": order_id})

# 호출: 의미가 명확
set_order_status("ORD-001", OrderStatus.Shipped)

이제 status_code=2가 무엇인지 별도 주석·코드북 없이 OrderStatus.Shipped로 자기 설명이 된다. 컴파일러(또는 런타임)가 유효하지 않은 값을 거부한다.

3. 분석 — Redis 공유 키

두 서비스 A·B가 Redis 같은 키에 쓰고 상대방의 변경을 즉시 읽는다는 강한 일관성 가정 하에 동일 공생성이다. 정확히 같은 Redis 인스턴스의 상태를 공유하고, 한 서비스의 쓰기가 다른 서비스에 즉시 관찰되어야 하기 때문이다. 만약 두 서비스가 메시지 버스로 통신하고 트랜잭션 일관성을 가정하지 않는다면, 동일 공생성이 아닐 수 있다.

4. 판단 — 타이밍 공생성 완전 제거

아니다 — 비즈니스 요구사항에서 비롯된 타이밍 공생성은 제거할 수 없다. DB 연결 30초 타임아웃은 "연결 후 30초 동안 아무 쿼리도 없으면 연결을 닫는다"는 요구사항 자체가 두 동작(연결 열기·타임아웃 닫기) 사이의 정확한 시간 간격에 의존하므로, 아무리 리팩토링해도 그 30초 의존은 제거할 수 없다. 제거할 수 있는 타이밍 공생성은 구현 부주의로 생긴 것(예: 클록 이중 조회)이며, 비즈니스 요구 자체로 생긴 타이밍 의존은 관리 대상이지 제거 대상이 아니다.

5. 종합 — process_payment 공생성 분석

공생성 낮추는 방법
이름 공생성 실질적으로 낮출 수 없음 — 이름 없이 참조 불가
유형 공생성 타입 힌트·계약 명시로 명확화는 가능, 완전 제거 불가
의미 공생성 transaction_type=3TransactionType.Charge 열거형으로 낮춤
알고리즘 공생성 암호화 알고리즘을 공유 라이브러리 상수로 단일화
위치 공생성 반환값 (res, balance, tran_id) → Named 튜플 또는 데이터 클래스로 낮춤

이름·유형 공생성은 모듈 간 통신의 기본 비용이므로 완전 제거는 불가. 의미·위치·알고리즘 공생성은 리팩토링으로 낮출 수 있다.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플